package com.mtzqc.common.task

import android.util.Log
import com.blankj.utilcode.util.ThreadUtils
import com.mtzqc.common.app.App
import io.reactivex.rxjava3.core.Observer
import io.reactivex.rxjava3.schedulers.Schedulers
import io.reactivex.rxjava3.subjects.PublishSubject
import kotlin.system.measureTimeMillis

class TaskHelper {
    private val completedTasks: MutableSet<String> = mutableSetOf()
    private val allTasks: MutableList<String> = mutableListOf()
    private val tasksMapping: MutableMap<String, TaskInfo> = mutableMapOf()
    private val mTag = TaskHelper::class.java.simpleName
    private var observer: Observer<String>? = null
    private fun initTasks(tasks: List<ITask>) {
        if (App.isDebug()) {
            tasks.forEach {
                Log.d(
                    mTag,
                    "load service name: ${it.depName} class:  ${it.javaClass.name} main thread:  ${it.mainThread()} dep: ${it.depends}  ${it.dependsLib}"
                )
            }
        }
        var temps = tasks.toMutableList()
        temps.sort()
        val tempList: MutableList<ITask> = mutableListOf()
        temps.forEach { s ->
            s.depName.let { name ->
                if (!allTasks.contains(name)) {
                    allTasks.add(name)
                    tempList.add(s)
                } else {
                    Log.w(
                        mTag,
                        "hase eq service name: ${name} class:  ${s.javaClass.name} main thread:  ${s.mainThread()} dep: ${s.depends}  ${s.dependsLib}"
                    )
                }
            }
        }
        tempList.forEach {
            tasksMapping[it.depName] = TaskInfo(it, taskDeps(it, tasks))
        }
    }

    private fun taskDeps(service: ITask, services: List<ITask>): Set<String> {
        var deps = mutableSetOf<String>()
        var depends = service.depends
        if (!depends.isNullOrEmpty()) {
            depends.forEach { name ->
                if (!allTasks.contains(name)) {
                    throw RuntimeException("not find depend $name")
                }
            }
            deps.addAll(depends)
        }
        var libs = service.dependsLib
        if (!libs.isNullOrEmpty()) {
            libs.forEach { se ->
                var name = services.firstOrNull {
                    se.isAssignableFrom(it.javaClass)
                }?.depName
                if (null == name || !allTasks.contains(name)) {
                    throw RuntimeException("lib ${service.depName} not find depend lib ${se.name}")
                }
                deps.add(name)
            }
        }
        return deps
    }

    private fun checkCircularDependency(
        chain: List<String>,
        depends: Set<String>
    ) {
        depends.forEach { depend ->
            check(chain.contains(depend).not()) {
                "Found circular dependency chain: $chain -> $depend"
            }
            checkCircularDependency(chain + depend, tasksMapping[depend]!!.depends)
        }
    }


    fun startTask(tasks: List<ITask>) {
        /**
         * 初始化所有服务
         */
        initTasks(tasks)
        /**
         * 检查是否循环依赖
         */
        tasksMapping.forEach {
            checkCircularDependency(listOf(it.key), it.value.depends)
        }
        //同步
        val singleSyncTasks: MutableList<TaskInfo> = mutableListOf()
        //异步
        val singleAsyncTasks: MutableList<TaskInfo> = mutableListOf()
        allTasks.forEach {
            val taskInfo = tasksMapping[it]!!
            when {
                taskInfo.depends.isNotEmpty() -> {
                    taskInfo.depends.forEach { dep ->
                        val depend = tasksMapping[dep]!!
                        depend.children.add(taskInfo)
                    }
                }
                taskInfo.mainThread -> {
                    singleSyncTasks.add(taskInfo)
                }
                else -> {
                    singleAsyncTasks.add(taskInfo)
                }
            }
        }
        var subject = PublishSubject.create<String>()
        subject.observeOn(Schedulers.single()).subscribe {
            execute(it)
        }
        observer = subject
        singleAsyncTasks.forEach {
            subject.onNext(it.depName)
        }
        singleSyncTasks.forEach {
            execute(it)
        }
    }

    private fun execute(name: String) {
        execute(tasksMapping[name]!!)
    }

    private fun execute(task: TaskInfo) {
        var result: Boolean
        val cost = measureTimeMillis {
            result = task.task.executeTask()
        }
        if (App.isDebug()) {
            Log.d(
                mTag,
                "Execute task [${task.depName}] complete result $result thread [${Thread.currentThread().name}], cost: ${cost}ms"
            )
        }
        if (!result) {
            return
        }
        val nextTasks = synchronized(completedTasks) {
            completedTasks.add(task.depName)
            task.children.filter { completedTasks.containsAll(it.depends) }
        }
        if (nextTasks.isNotEmpty()) {
            nextTasks.sortedBy {
                it.priority
            }.forEach { info ->
                if (ThreadUtils.isMainThread() == info.mainThread) {
                    execute(info)
                } else if (info.mainThread) {
                    ThreadUtils.getMainHandler().post {
                        execute(info)
                    }
                } else {
                    observer?.onNext(info.depName)
                }
            }
        }
        if (allTasks.size == completedTasks.size) {
            observer?.let {
                observer = null
                it.onComplete()
                if (App.isDebug()) {
                    Log.d(
                        mTag, "all task init completed ${Thread.currentThread().name}"
                    )
                }
            }
        }
    }

}